GitHub

您所在的位置:网站首页 k8s in action 2 status GitHub

GitHub

2023-09-27 01:47| 来源: 网络整理| 查看: 265

2. Get Started 2.1 Docker 容器 > docker run busybox ls -lh # 运行标准的 unix 命令 > docker run : # 运行指定版本的 image,tag 默认 latest # Dockerfile 包含构建 docker 镜像的命令 FROM node # 基础镜像 ADD app.js /app.js # 将本地文件添加到镜像的根目录 ENTRYPOINT ["node", "app.js"] # 镜像被执行时需被执行的命令 > docker build -t kubia . # 在当前目录根据 Dockerfile 构建指定 tag 的镜像 > docker images # 列出本地所有镜像 # 执行基于 kubia 镜像,映射主机 8081 到容器内 8080 端口,并在后台运行的容器 > docker run --name kubia-container -p 8081:8080 -d kubia > docker ps # 列出 running 容器 > docker ps -a # 列出 running, exited 容器 > docker exec -it kubia-container bash # 在容器内执行 shell 命令,如 ls/sh > docker stop kubia-container # 停止容器 > docker rm kubia-container # 删除容器 > docker tag kubia wuyinio/kubia # 给本地镜像打标签 > docker login > docker push wuyinio/kubia # push 到 DockerHub 2.2 配置 k8s 集群 > minikube start # 本地启动 minikube 单节点虚拟机 > kubectl cluster-info # 查看集群各组件的 URL,是否工作正常 > kubectl get nodes # get 命令可列出各种 k8s 对象的基本信息 > kubectl describe node # describe 命令显示 k8s 对象更详细的信息 2.3 在 k8s 上运行应用 > kubectl run kubia --image=wuyinio/kubia --port=8080 --generator=run/v1 # 创建 rc 并拉取镜像运行 > kubectl get pods # 列出 pods > kubectl expose rc kubia --type=LoadBalancer --name kubia-http # 通过 LoadBalancer 服务暴露 ClusterIP pod 服务给外部访问 > kubectl get svc # 列出 services > kubectl get rc # 列出 replication controller > minikube service kubia-http # minikube 单节点不支持 LoadBalancer 服务,需手动获取服务地址 # kubia-http service 的 EXTERNAL_IP 一直为 PENDING 状态 > kubectl scale rc kubia --replicas=5 # 修改 rc 期望的副本数,来水平伸缩应用 > kubectl get pod kubia-1ic8j -o wide # 显示 pod 详细列 3. Pod 3.2 创建 Pod

yaml pod 定义模块

apiVersion 与 kind 资源类型。 metadata 元数据: pod 名称、标签、注解。 spec 规格内部元件信息:容器镜像名称、卷等。

kubia-manual.yaml

apiVersion: v1 kind: Pod metadata: name: kubia-manual spec: containers: - image: wuyinio/kubia:2 name: kubia ports: - containerPort: 8080 # pod 对外暴露的端口 protocol: TCP # supported values: "SCTP", "TCP", "UDP" > kubectl create -f kubia-manual.yaml # 从 yaml 文件创建 k8s 资源 > kubectl get pod kubia-manual -o yaml # 导出 pod 定义 > kubectl logs kubia-manual -c kubia # 查看 kubia-manual Pod 中 kubia 容器的日志,-c 显式指定容器名称 > kubectl logs kubia-manual --previous # 查看崩溃前的上一个容器日志 > minikube ssh && docker ps > docker logs bdb67198848d # 登录到 pod 运行时的节点 minikube,手动 docker logs 查看日志 > kubectl port-forward kubia-manual 9090:8080 # 配置多重端口转发,将本机的 9090 转发至 pod 的 8080,可用于调试等 port-forward kubia-manual 9090:8080 Forwarding from 127.0.0.1:9090 -> 8080 Forwarding from [::1]:9090 -> 8080 Handling connection for 9090 # curl 127.0.0.1:9090 3.3 标签(label)

基于组操作 pod 而非单个操作,metadata.labels 的 kv pair 标签可组织任何 k8s 资源,保存标识信息。

apiVersion: v1 kind: Pod metadata: name: kubia-manual-v2 labels: creation_method: manual env: prod spec: # ... # 基于 Label 的增删改查操作 > kubectl get pods --show-labels # 显示 labels > kubectl get pods -L env # 只显示 env 标签 > kubectl label pod kubia-manual env=debug # 为指定的 pod 资源添加新标签 > kubectl label pod kubia-manual env=online --overwrite=true # 修改标签 > kubectl label pod kubia-manual env- # - 号删除标签 3.5 标签选择器(nodeSelector)

label selector 可筛选出具有指定值的 k8s 资源。

> kubectl get pods -l env=debug # 筛选 env 为 debug 的 pods # get pod 与 get pods 无异 > kubectl get pod -l creation_method!=manual # 不等 > kubectl get pods -l '!env' # 不含 env 标签的 pods # -l 筛选 -L 显示 # "" 双引号会转义 > kubectl get pods -l 'env in (debug)' # in 筛选 # -l 接受整体字符串为参数 > kubectl get pods -l 'env notin (debug,online)' # notin 筛选

在指定标签 node 上运行 pod:

apiVersion: v1 kind: Pod metadata: name: kubia-gpu spec: nodeSelector: gpu: "true" # 当无可用 node 时 pod 一直处于 Pending 状态 containers: #...

可以使用 kubernetes.io/hostname: minikube 的 nodeSelector 将 pod 运行在指定的物理机上(不建议)

3.6 注解(annotation)

类似 label 的 kv pair 注释,但不用作标识,用作资源说明(所以才会放在 pod metadata 的第一个子节点),可添加大量的数据块:

# 增删改查和 label 操作一样 > kubectl annotate pod kubia-gpu yinzige.com/gpu=10G > kubectl describe pod kubia-gpu # 出现在 metadata.annotation

注:为避免标签或注解冲突,和 Java Class 使用倒序域名的方式类似,建议 key 中添加域名信息。

3.7 命名空间(namespace)

labels 会导致资源重叠,可用 namespace 将对象分配到集群级别的隔离区域,相当于多租户的概念,以 namespace 为操作单位。

> kubectl get ns # 获取所有 namespace,默认 default 下 > kubectl get pods -n kube-system # 获取指定 namespace 下的资源 # kubectl create namespace custom-namespace # 创建 namespace 资源 apiVersion: v1 kind: Namespace metadata: name: custom-namespace apiVersion: v1 kind: Pod metadata: name: kubia-manual namespace: custom-namespace # 创建资源时在 metadata.namespace 中指定资源的命名空间 spec: # ... # 切换上下文命名空间 > kubectl config set-context $(kubectl config current-context) --namespace custom-namespace 3.8 删除 pod

删除原理:向 pod 所有容器进程定期发送 SIGTERM 信号,超时则发送 SIGKILL 强制终止。需在程序内部捕捉信号正确处理,如 Go 注册捕捉信号 signal.Notify() 后 select 监听该 channel

> kubectl delete pod -l env=debug # 删除指定标签的 pod > kubectl delete pod --all # 删除当前 namespace 下的所有 pod (慎用) > kubectl delete all --all # 删除所有类型资源的所有对象(慎用) 4. ReplicationController 4.1 容器存活探针(Liveness Probe)

将进程的重启监控从程序监控级别提升到 k8s 集群功能级别,使进程 OOM,死锁或死循环时能自动重启。pod 中各容器的探针用于暴露给 k8s ,来检查容器中的应用进程是否正常。分为 3 类:

HTTP Get:指定 IP:Port 和 Path,GET 请求返回 5xx 或超时则认为失败。 TCP Socket:是否能建立 TCP 连接。 Exec:在容器中执行任意命令,检查 $? 是否为 0 apiVersion: v1 kind: Pod metadata: name: kubia-liveness spec: containers: - image: wuyinio/kubia-unhealthy name: kubia livenessProbe: httpGet: # 定义 http get 探针 path: / # 指定路径和端口号 port: 8080 initialDelaySeconds: 11 # 初次探测延迟 11s

存活探针原则:

为检查设立子路径 /health ,确保无认证。 保证探针返回失败时,错误发生在应用内且重启可恢复,而非应用外的组件导致的失败,那重启也没用。

注:非托管 Pod 仅由 Worker 节点的 kubelet 负责通过探针监控并重启,但整个节点崩溃会丢失该 Pod

4.2 ReplicationController

RC 监控 Pod 列表并根据模板增删、迁移 Pod。分为 3 部分:

标签选择器 label selector:确定要管理哪些 pod 副本数量 replica count:指定要运行的 pod 数量 pod 模板 pod template:创建新的 pod 副本

注:修改标签选择器,会导致 rc 不再关注之前匹配的所有 pod。修改模板则只对新 Pod 生效(如手动 delete)

RC 的两个功能:

监控:确保符合标签选择器的 Pod 以指定的副本数量运行,多了则删除,少了则按 Pod 模板创建。 扩缩容:能对监控的某组 Pod 进行动态修改副本数量进行扩缩容。 apiVersion: v1 kind: ReplicationController metadata: name: kubia spec: replicas: 3 # pod 实例的期望数 selector: # 决定 rc 的操作对象,可省略。必须与模板 label 匹配,否则 `selector` does not match template `labels` app: kubia template: metadata: labels: app: kubia spec: containers: - name: kubia image: wuyinio/kubia ports: - containerPort: 8080

操作 RC:

> kubectl describe rc kubia # 查看 rc 详细信息如 events > kubectl edit rc kubia # 修改 rc 的 yaml 配置 > kubectl scale rc kubia --replicas=5 # 扩缩容 > kubectl delete rc kubia --ascade=false # 删除 rc 时保留运行中的 pod # ascade 级联(关联删除) 4.3 ReplicaSet

ReplicaSet = ReplicationController + 扩展的 label selector ,即对 pod 的 label selector 表达力更强 。

RS 能通过 selector.matchLabels 和 selector.matchExpressio 来扩展对 pod label 的筛选:

apiVersion: apps/v1 kind: ReplicaSet metadata: name: kubia spec: replicas: 3 selector: matchLabels: # 与 RC 一样必须完整匹配 app: kubia matchExpressions: - key: app operator: In # 必须有 KEY 且 VALUE 在列表中 values: - kubia - kubia-v2 - key: app operator: NotIn # 有 KEY 则不能在如下列表中 values: - KUBIA - key: env # 必须存在的 KEY,不能有 VALUE operator: Exists - key: ENV operator: DoesNotExist # 必须不能存在的 KEY,也不能有 VALUE template: metadata: labels: # Pod 模板的 label 必须能和 RS 的 selector 匹配上 app: kubia env: env_exists spec: containers: - name: kubia image: yinzige/kubia ports: - protocol: TCP containerPort: 8080 4.4 DaemonSet 功能:保证标签匹配的 Pod 在符合 selector 的一个节点上运行一个,没有目标 pod 数量的概念,无法 scale 场景:部署系统级组件,如 Node 监控,如 kube-proxy 处理各节点网络代理等。 apiVersion: apps/v1 kind: DaemonSet metadata: name: ssd-monitor spec: selector: matchLabels: app: ssd-monitor # 指定要控制运行的一组 Pod template: metadata: labels: app: ssd-monitor # 被控制的 Pod 的标签 spec: nodeSelector: # 选择 Pod 要运行的节点标签 disk: ssd # 注意 YAML 文件的 true 类型是布尔型,如 ssd: true 是无法被解析为 String 的 containers: - name: main image: yinzige/ssd-monitor 4.5 Job 功能:保证任务以指定的并发数执行指定次数,任务执行失败后按配置策略处理。 场景:执行一次性任务。 apiVersion: batch/v1 kind: Job metadata: name: batch-job spec: completions: 5 # 总任务数量 parallelism: 2 # 并发执行任务数 template: metadata: labels: app: batch-job # 要执行的 pod job label spec: restartPolicy: OnFailure # 任务异常结束或节点异常时处理方式:"Always", "OnFailure", "Never" containers: - name: main image: yinzige/batch-job 4.6 CronJob

对标 Linux 的 crontab 的定时任务。

apiVersion: batch/v1beta1 kind: CronJob metadata: name: cron-batch-job spec: schedule: "*/1 * * * *" # 每分钟运行一次 jobTemplate: spec: template: metadata: labels: app: cron-batch-job spec: restartPolicy: OnFailure containers: - name: cron-batch-job image: yinzige/batch-job 5. Service 5.1 Service 内部解析 接入点隔离

由于 pod 调度后 IP 会变化,需使用 Service 服务给一组 Pod 提供不变的单一接入点 entrypoint,即 IP:Port

> kubectl expose rc kubia --type=LoadBalancer --name kubia-http # 通过服务暴露 ClusterIP pod 服务给外部访问

创建名为 kubia 的 Service,将其 80 端口的请求分发给有 app: kubia 标签 Pod 的自定义的 http 端口上:

apiVersion: v1 kind: Service metadata: name: kubia spec: sessionAffinity: ClientIP ports: - name: http port: 80 targetPort: http - name: https port: 443 targetPort: https # defined in pod template.spec.containers.ports array selector: app: kubia

设置请求亲和性:保证一个 Client 的所有请求都只会落到同一个 Pod 上:

apiVersion: v1 kind: Service metadata: name: kubia-svc-session spec: sessionAffinity: ClientIP # or None default ports: - port: 80 targetPort: 8080 服务发现

客户端和 Pod 都需知道服务本身的 IP 和 Port,才能与其背后的 Pod 进行交互。

环境变量:kubectl exec kubia-qgtmw env 会看到 Pod 的环境变量列出了 Pod 创建时的所有服务地址和端口,如 SVCNAME_SERVICE_HOST 和 SVCNAME_SERVICE_PORT 指向服务。

DNS 发现:Pod 上通过全限定域名 FQDN 访问服务:..svc.cluster.local

> kubectl exec kubia-qgtmw cat /etc/resolv.conf nameserver 10.96.0.10 search default.svc.cluster.local svc.cluster.local cluster.local # 会 options ndots:5

在 Pod kubia-qgtmw 中可通过访问 kubia.default.svc.cluster.local 来访问 kubia 服务,在 /etc/resolv.conf 中指明了域名解析服务器地址,以及主机名补全规则,是在 Pod 创建时候,根据 namespace 手动导入的。

5.2 Service 对内部解析外部

集群内部的 Pod 不直连到外部的 IP:Port,而是同样定义 Service 结合外部 endpoints 做代理中间层解耦。如获取百度首页的向外解析:

1.1 建立外部目标的 endpoints 资源:

apiVersion: v1 kind: Endpoints metadata: name: baidu-endpoints subsets: - addresses: - ip: 220.181.38.148 # baidu.com - ip: 39.156.69.79 ports: - port: 80

1.2 或者建立外部解析别名

apiVersion: v1 kind: Service metadata: name: baidu-endpoints spec: type: ExternalName externalName: www.baidu.com ports: - port: 80 再建立同名的 Service 代理,标识使用上边这组 endpoints apiVersion: v1 kind: Service metadata: name: baidu-endpoints spec: ports: - port: 80 效果:在集群内部 Pod 上可透过名为 baidu-endpoints 的 Service 连接到百度首页: # root@kubia-72sxt:/# curl 10.103.134.52 root@kubia-72sxt:/# curl baidu-endpoints

注意 Service 类型:

> kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE baidu-endpoints ExternalName www.baidu.com 80/TCP 30m kubernetes ClusterIP 10.96.0.1 443/TCP 5d2h kubia ClusterIP 10.96.239.1 80/TCP,443/TCP 87m 5.3 Service 对外部解析内部 5.3.1 NameNode

使用:外部客户端直连宿主机端口访问服务。

原理:在集群所有节点暴露指定的端口给外部客户端,该端口会将请求转发 Service 进一步转发给节点上符合 label 的 Pod,即 Service 从所有节点收集指定端口的请求并分发给能处理的 Pod

缺点:高可用性需由外部客户端保证,若节点下线需及时切换。

apiVersion: v1 kind: Service metadata: name: kubia-nodeport spec: type: NodePort ports: - port: 80 targetPort: 8080 nodePort: 30001 selector: app: kubia

三个端口号,节点转发 30001,Service 转发 80:

宿主机即外部,执行 curl MINIKUBE_NODE_IP:30001 会被转发到有 app:kubia 标签的 Pod 的 8080 端口。执行 curl NAME_PORT:80 同理。

5.3.2 LoadBalancer

场景:外部客户端直连 LB 访问服务。其是 K8S 集群端高可用的 NameNode 扩展。

apiVersion: v1 kind: Service metadata: name: kubia-loadbalancer spec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: kubia

k8s app:kubia 所在的所有节点打开随机端口 32148,进一步转发给 Pod 的 8080 端口。

kubia-loadbalancer LoadBalancer 10.108.104.22 80:32148/TCP 4s 5.4 Ingress

顶级转发代理资源,仅通过一个 IP 即可代理转发后端多个 Service 的请求。需要开启 nginx controller 功能

apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: kubia spec: rules: - host: "kubia.example.com" http: paths: - path: /kubia # 将 /kubia 子路径请求转发到 kubia-nodeport 服务的 80 端口 backend: serviceName: kubia-nodeport servicePort: 80 - path: /user # 可配置多个 path 对应到 service backend: serviceName: user-svc servicePort: 90 - host: "new.example.com" # 可配置多个 host http: paths: - path: / backend: serviceName: gooele servicePort: 8080 5.5 就绪探针

场景:pod 启动后并非立刻就绪,需延迟接收来自 service 的请求。若不定义就绪探针,pod 启动就会暴露给 service 使用,所以需像存活探针一样添加指定类型的就绪探针:

#... spec: containers: - name: kubia-container image: yinzige/kubia readinessProbe: exec: command: - ls - /var/ready_now 5.6 headless

场景:向客户端暴露所有 pod 的 IP,将 ClusterIP 置为 None 即可:

apiVersion: v1 kind: Service metadata: name: kubia-headless spec: clusterIP: None ports: - port: 80 targetPort: 8080 selector: app: kubia

k8s 不会为 headless 服务分配 IP,通过 DNS 可直接发现后端的所有 Pod

> kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubia LoadBalancer 10.104.138.112 80:32110/TCP 123m kubia-headless ClusterIP None 80/TCP 10m root@dnsutils:/# nslookup kubia-headless Server: 10.96.0.10 Address: 10.96.0.10#53 Name: kubia-headless.default.svc.cluster.local Address: 172.17.0.6 Name: kubia-headless.default.svc.cluster.local Address: 172.17.0.12 Name: kubia-headless.default.svc.cluster.local Address: 172.17.0.10 Name: kubia-headless.default.svc.cluster.local Address: 172.17.0.8 root@dnsutils:/# nslookup kubia Server: 10.96.0.10 Address: 10.96.0.10#53 Name: kubia.default.svc.cluster.local Address: 10.104.138.112 Ch6. Volume 6.1 卷介绍 问题:Pod 中每个容器的文件系统来自镜像,相互独立。 解决:使用存储卷,让容器访问外部磁盘空间、容器间共享存储。

卷是 Pod 生命周期的一部分,不是 k8s 资源对象。Pod 启动时创建,删除时销毁(文件可选保留)。用于 Pod 中挂载到多个容器进行文件共享。

卷类型:

emptyDir:存放临时数据的临时空目录。 hostPath:将 k8s worker 节点的系统文件挂载到 Pod 中,常用于单节点集群的持久化存储。 persistentVolumeClaim:PVC 持久卷声明,用于预配置 PV 6.2 emptyDir 卷

1 个 Pod 中 2 个容器使用同一个 emptyDir 卷 html,来共享文件夹,随 Pod 删除而清除,属于非持久化存储。

apiVersion: v1 kind: Pod metadata: name: fortune spec: containers: - name: html-generator image: yinzige/fortuneloop volumeMounts: - name: html mountPath: /var/htdocs # 将 html 的卷挂载到 html-generator 容器的 /var/htdocs 目录 - name: web-server image: nginx:alpine volumeMounts: - name: html mountPath: /usr/share/nginx/html # 将 html 的卷挂载 web-server 容器到 /usr/share/nginx/html 目录 readOnly: true # 设置只读 volumes: - name: html # 声明名为 emptyDir 的 emptyDir 卷 emptyDir: {}

emptyDir 卷跟随 Pod 被 k8s 自动分配在宿主机指定目录:/var/lib/kubelet/pods/PODUID/volumes/kubernetes.io~empty-dir/VOLUMENAME

如上的 html 卷位置在 minikube 节点:

$ sudo ls -l /var/lib/kubelet/pods/144c55eb-edf5-4b44-a2f6-a0d9cfe04f7c/volumes/kubernetes.io~empty-dir/html total 4 -rw-r--r-- 1 root root 80 Apr 26 05:01 index.html 6.3 hostPath 卷

hostPath 卷的数据不跟随 Pod 生命周期,下一个调度至此节点的 Pod 能继续使用前一个 Pod 留下的数据,pod 和节点是强耦合的,只适合单节点部署。

6.5 持久化卷 PV、持久化卷声明 PVC

PV 与 PVC 用于解耦 Pod 与底层存储。PV、PVC 与底层存储关系:

流程:

管理员向集群加入节点时准备 NFS 等存储资源(TODO ) 管理员创建指定大小和访问模式的 PV 用户创建需要大小的 PVC K8S 寻找符合 PVC 的 PV 并绑定 用户在 Pod 中通过卷引用 PVC,从而使用存储 PV 资源

Admin 通过网络存储创建 PV:

apiVersion: v1 kind: PersistentVolume # 创建持久卷 metadata: name: mongodb-pv spec: capacity: storage: 1Gi # 告诉 k8s 容量大小和多个客户端挂载时的访问模式 accessModes: - ReadWriteOnce - ReadOnlyMany persistentVolumeReclaimPolicy: Retain # Cycle / Delete 标识 PV 删除后数据的处理方式 hostPath: # 持久卷绑定到本地的 hostPath path: /tmp/mongodb

User 通过创建 PVC 来找到大小、容量均匹配的 PV 并绑定:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mongodb-pvc # pvc 名称将在 pod 中引用 spec: resources: requests: storage: 1Gi accessModes: - ReadWriteOnce storageClassName: "" # 手动绑定 PVC 到已存在的 PV,否则有值就是等待绑定到匹配的新 PV

User 创建 Pod 使用 PVC:

kind: Pod metadata: name: mongodb spec: containers: - image: mongo name: mongodb volumeMounts: - name: mongodb-data mountPath: /tmp/data ports: - containerPort: 27017 protocol: TCP volumes: - name: mongodb-data persistentVolumeClaim: # pod 中通过 claimName 指定要引用的 PVC 名称 claimName: mongodb-pvc

PV 设置卷的三种访问模式:

RWO:ReadWriteOnly:仅允许单个节点挂载读写 ROX:ReadOnlyMany :允许多个节点挂载只读 RWX:ReadWriteMany:允许多个节点挂载读写

注:PV 是集群级别的存储资源,PVC 和 Pod 是命名空间范围的。所以,在 A 命名空间的 PVC 和在 B 命名空间的 PVC 都有可能绑到同一个 PV 上。

6.6 动态 PV:存储类 StorageClass

场景:进一步解耦 Pod 与 PVC,使 Pod 不依赖 PVC 名称,而且跨集群移植只需保证 SC 一致即可,不用管 PVC 和 PV。同时还能给不同 PV 进行归档如按硬盘属性进行分类。

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast provisioner: k8s.io/minikube-hostpath # 指定 SC 收到创建 PVC 请求时应调用哪个组件进行处理并返回 PV parameters: type: pd-ssd

总流程:可创建 StorageClass 存储类资源,用于分类 PV,在 PVC 中绑定到符合条件的 PV 上。

7. 配置传递:ConfigMap 与 Secret 7.1 配置容器化应用程序

三种配置方式:

向容器传递命令行参数。 为每个容器设置环境变量。 通过卷将配置文件挂载至容器中。

容器传递配置文件的问题:修改配置需重新构建镜像,配置文件完全公开。解决:配置使用 ConfigMap 或 Secret 卷挂载。

7.2 向容器传递命令行参数

Dockerfile 中 ENTRYPOINT 为命令,CMD 为其参数。但参数能被 docker run 中的参数覆盖。

ENTRYPOINT ["/bin/fortuneloop.sh"] # 在脚本中通过 $1 获取 CMD 第一个参数,Go 中 os.Args[1] 类似 CMD ["10", "11"]

二者等同于 Pod 中的 command 和 args,但 pod 可通过 image 的 command 和 args 子标签进行覆盖,注意参数必须是字符串:

apiVersion: v1 kind: Pod metadata: name: fortune2s spec: containers: - image: luksa/fortune:args args: ["2"] # ... 7.3 为容器设置环境变量

只能在各容器级别注入环境变量,而非 Pod 级别。配置容器部分 spec.containers.env 指定即可:

apiVersion: v1 kind: Pod metadata: name: fortune3s spec: containers: - image: luksa/fortune:env env: - name: INTERVAL # 对应到容器 html-generator 中的 $INTERVAL value: "5" - name: "NESTED_VAR" value: "$(INTERVAL)_1" # 可引用其他环境变量 name: html-generator # ...

缺点:硬编码环境变量可能在多个环境下值不同无法复用,需将配置项解耦。

7.4 ConfigMap 卷

存储非敏感信息的文本配置文件。

# 创建 cm 的四种方式:可从 kv 字面量、配置文件、有命名配置文件、目录下所有配置文件 > kubectl create configmap fortune-config --from-literal=sleep-interval=25 # 从 kv 字面量创建 cm > kubectl create configmap fortune-config --from-file=nginx-conf=my-nginx-config.conf # 指定 k 的文件创建 cm

两种方式将 cm 中的值传递给 Pod 中的容器:

7.4.1 设置环境变量或命令行参数 apiVersion: v1 kind: Pod metadata: name: fortune-env-from-configmap spec: containers: - image: luksa/fortune:env name: html-generator env: - name: INTERVAL # 取 CM fortune-config 中的 sleep-interval,作为 html-generator 容器环境变量 INTERVAL 的值 valueFrom: configMapKeyRef: name: fortune-config-cm key: sleep-interval envFrom: # 批量导入 cm 的所有 kv 作为环境变量,并加上前缀 - prefix: CONF_ configMapRef: name: fortune-config-cm # ...

可使用 kubectl get cm fortune-config -o yaml 查看 CM 的 data 配置项。

7.4.2 配置 ConfigMap 卷

当配置项过长需放入配置文件时,可将配置文件暴露为 cm 并用卷引用,从而在各容器内部挂载读取。

apiVersion: v1 kind: Pod metadata: name: fortune-configmap-volume spec: containers: - name: html-generator image: yinzige/fortuneloop:env env: - name: INTERVAL valueFrom: configMapKeyRef: key: sleep-interval # raw key file name name: fortune-config # cm name volumeMounts: - mountPath: /var/htdocs name: html - name: web-server image: nginx:alpine volumeMounts: - mountPath: /usr/share/nginx/html name: html readOnly: true - mountPath: /etc/nginx/conf.d/gzip_in.conf name: config subPath: gzip.conf # 使用 subPath 只挂载部分卷 gzip.conf 到指定目录下指定文件 gzip_in.conf readOnly: true ports: - containerPort: 80 name: http protocol: TCP volumes: - name: html emptyDir: {} - name: config configMap: name: fortune-config # cm name defaultMode: 0666 # 设置卷文件读写权限 items: # 使用 items 限制从 cm 暴露给卷的文件 - key: my-nginx-config.conf path: gzip.conf # 把 key 文件的值 copy 一份到新文件中

添加 items 来暴露指定的文件到卷中,subPath 用来挂载部分卷,而不隐藏容器目录原有的初始文件。

> kubectl exec fortune-configmap-volume -c web-server -it -- ls -lA /etc/nginx/conf.d total 8 -rw-r--r-- 1 root root 1093 Apr 14 14:46 default.conf # subPath -rw-rw-rw- 1 root root 242 Apr 27 16:49 gzip_in.conf 7.4.3 ConfigMap 场景

使用 kubectl edit cm fortune-config 修后,容器中对应挂载的卷文件会延迟将修改同步。问题:若 pod 应用不支持配置文件的热更新,那同步了的修改并不会再旧 pod 生效,反而新起的 pod 会生效,造成新旧配置共存的问题。

场景:cm 的特性是不变性,若 pod 应用本身支持热更新,则可修改 cm 动态更新,但注意有 k8s 的监听延迟。

7.5 Secret

存储敏感的配置数据,大小限制 1MB,其配置条目会以 Base64 编码二进制后存储:

> kubectl create secret generic fortune-auth --from-file=fortune-auth/ # password.txt

在 pod 中加载:

apiVersion: v1 kind: Pod metadata: name: fortune-with-serect spec: containers: - name: fortune-auth-main image: yinzige/fortuneloop volumeMounts: - mountPath: /tmp/no_password.txt subPath: password.txt name: auth volumes: - name: auth secret: secretName: fortune-auth

读取正常:

> kubectl exec fortune-with-serect -it -- ls -lh /tmp total 4.0K -rw-r--r-- 1 root root 10 Apr 27 17:51 no_password.txt > kubectl exec fortune-with-serect -it -- mount | grep password tmpfs on /tmp/no_password.txt type tmpfs (ro,relatime) # secret 仅存储在内存中

secret 可用于从镜像仓库中拉取 private 镜像,需配置专用的 secret 使用:

apiVersion: v1 kind: Pod spec: imagePullSecrets: - name: dockerhub-secret containers: # ... 8. Pod Metadata 与 k8s API

场景:从 Pod 中的容器应用进程访问 Pod 元数据及其他资源。

8.1 使用 Downward API 传递 Pod Metadata

问题:配置数据如环境变量、ConfigMap 都是预设的,应用内无法直接获取如 Pod IP 等动态数据。

解决:Downward API 通过环境变量、downward API 卷来传递 Pod 的元数据。如:Pod 名称、IP、命名空间、标签、节点名、每个容器的 CPU、内存限制及其使用量。

8.1.1 环境变量透传

在 Pod 定义中手动将容器需要的元数据,以环境变量的形式手动透传给容器:

apiVersion: v1 kind: Pod metadata: name: downward spec: containers: - name: main image: busybox command: - "sleep" - "1000" env: - name: E_POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: E_POD_IP valueFrom: fieldRef: fieldPath: status.podIP # 运行时元数据 - name: E_REQ_CPU valueFrom: resourceFieldRef: # 引用容器级别的数据,如请求的 CPU、内存用量等需引用 resourceFieldRef resource: requests.cpu divisor: 1m # 资源单位 - name: E_LIMIT_MEM valueFrom: resourceFieldRef: resource: limits.memory divisor: 1Ki

效果:

k exec downward -it -- env | grep -e "^E_" E_POD_NAMESPACE=default E_POD_IP=172.17.0.8 E_REQ_CPU=0 E_LIMIT_MEM=2085844

缺点:无法通过环境传递 pod 标签和注解等可在运行时动态修改的元数据。

ERROR: error converting fieldPath: field label not supported: metadata.lebels.app 8.1.2 通过 downwardAPI 卷 apiVersion: v1 kind: Pod metadata: name: downward labels: foo: bar annotations: k1: v1 spec: containers: - name: main # ... volumeMounts: - name: downward mountPath: /etc/downward volumes: - name: downward # 定义一个名为 downward 的 DownwardAPI 卷,将元数据写入 items 下指定路径的文件中 downwardAPI: items: - path: "container_request_memory" resourceFieldRef: containerName: main # 容器级别的元数据需指定容器名 resource: requests.cpu divisor: 1m divisor: 1m - path: "labels" # "annotations" # pod 的标签和注解必须使用 downwardAPI 卷去访问 fieldRef: fieldPath: metadata.labels

效果:k8s 会自动地将 pod 的标签和注解同步到 downward API 卷的指定文件中。

> kubectl exec downward-volume-pod -it -- ls /etc/downward container_request_memory pod_annotations pod_labels > kubectl exec downward-volume-pod -it -- cat /etc/downward/pod_annotations key1="VALUE1" key2="VALUE2\nVALUE20\nVALUE200\n" kubernetes.io/config.seen="2020-04-28T04:15:29.722938998Z" kubernetes.io/config.source="api" > kubectl annotate pod downward-volume-pod new_key=NEW_VALUE pod/downward-volume-pod annotated > kubectl exec downward-volume-pod -it -- tail -2 /etc/downward/pod_annotations kubernetes.io/config.source="api" new_key="NEW_VALUE" 8.2 与 k8s API 交互

问题:downward API 只能向应用暴露 1 个 Pod 的部分元数据,无法提供其他 Pod 和其他资源信息。

请求 k8s API:

外部:通过 kubectl proxy 中间请求转发代理。 内部:从 Pod 内验证 Secret 卷的 crt 证书、传递 token 到来对本 namespace 内的资源进行操作。

Pod 与 API 服务器交互流程:

应用通过 secret 卷下的 ca.crt 验证 API 地址 应用带上 TOKEN 授权 操作 pod 所在命名空间内的资源 > ls /var/run/secrets/kubernetes.io/serviceaccount/ ca.crt # 验证 API 服务器证书,避免中间人攻击 namespace # 获取本地 pod 的 namespace: default token # 通过 header 添加 "Authorization: Bearer $TOKEN" 方式来获取授权 > curl --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://kubernetes # 验证 API 地址 > export CURL_CA_BUNDLE=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt > TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) > curl -H "Authorization: Bearer $TOKEN" https://kubernetes # 授权 > NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) > curl -H "Authorization: Bearer $TOKEN" https://kubernetes/api/v1/namespaces/$NS/pods # 操作本地命名空间资源

折中的 Pod 内部公共透明代理模式:在 pod 内部通过 ambassador 公共容器简化 API 服务器验证流程。普通容器走 HTTP 到 ambassador 容器,后者走 secret 流程走 HTTPS 与 k8s API 交互。

9. Deployment

场景:定义比例滚动升级,出错自动回滚。

9.1 纯手动更新运行在 Pod 内的应用程序

版本升级方式:

先删旧 Pod,再自动创建新 Pod:存在不可用间隔。

修改 Pod spec.template 中的标签选择器,选择新版 tag 对应的应用,先手动删除旧版本,RS 自动创建新版容器。

创建新 Pod,同时逐步剔除旧 Pod:需两个版本应用都能对外服务,短时间内 Pod 数量翻倍。

修改 Service 的 pod selector 蓝绿部署进度可控地将流量切换到新 Pod 上。

9.2 基于 RC 的滚动升级

问题:手动脚本将旧 Pod 缩容,新 Pod 扩容,易出错。

解决:使用 kubectl 请求 k8s API 来执行滚动升级:

# 指定需更新的 RC kubia-v1,用指定的 image 创建的新 RC 来替换 > kubectl rolling-update kubia-v1 kubia-v2 --image=wuyinio/kubia:v2 # 从 1.8 已移除

过程:kubectl 为新旧 Pod、新旧 RC 添加 deployment 标签,并向 k8s API 请求对旧 Pod 进行缩容,对新 Pod 扩容,透明地将 service 的标签匹配到新 pod 上。

原理:由客户端 kebectl 动态地修改两个 RC 的标签,缩容旧 Pod,扩容新 Pod,最终完成流量转移。

9.3 使用 Deployment 声明式升级

问题:RC 滚动升级是 kubectl 客户端控制升级,若网络断开连接则升级中断(如关闭终端),Pod 和 RC 会处于多版本混合态。

解决:在服务端使用高级资源 Deployment 声明来协调新旧两个 RS 的扩缩容,用户只定义最终目标收敛状态。

deployment 也分为 3 部分:标签选择器、期望副本数、Pod 模板:

apiVersion: apps/v1 kind: Deployment metadata: name: kubia spec: replicas: 3 template: metadata: name: kubia labels: app: kubia spec: containers: - name: nodejs image: yinzige/kubia:v1 selector: matchLabels: app: kubia # 创建 deployment > kubectl create -f kubia-deployment-v1.yaml --record # record 选项将记录历史版本号,用于后续回滚 > kubectl rollout status deployment kubia # rollout 显示部署状态 # deployment 用 pod 模板的哈希值创建 RS,再由 RS 创建 Pod 并管理,哈希值一致 > kubectl get pods NAME READY STATUS RESTARTS AGE kubia-5b9f8f4d84-nxmqd 1/1 Running 0 2s kubia-5b9f8f4d84-q5wc5 1/1 Running 0 2s kubia-5b9f8f4d84-r866t 1/1 Running 0 2s > kubectl get rs NAME DESIRED CURRENT READY AGE kubia-5b9f8f4d84 3 3 3 9s

deployment 的升级策略:spec.stratagy

RollingUpdate:渐进式删除旧 Pod。新旧版混合 Recreate:一次性删除所有旧 Pod,重建新 Pod。中间服务不可用 9.3.1 触发滚动升级

先指定 Pod 就绪后的等待时间: kubectl patch deployment kubia -p '{"spec": {"minReadySeconds": 10}}'

修改某个容器的镜像来触发升级:kubectl set image deployment kubia nodejs=yinzige/kubia:v2

注:触发升级需真正修改到 deployment 的字段。

原理:kubectl get rs 可看到保留了的新旧版 rs,deployment 资源在 k8s master 端会自动控制新旧 RS 的扩缩容。

9.3.2 回滚 > kubectl rollout undo deployment kubia # 回滚到上一次 deployment 部署的版本 > kubectl rollout history deployment kubia # 创建 deployment 时 --record,此处显示版本 > kubectl rollout undo deployment kubia --to-reversion=1 # 回滚到指定 REVERSION,若手动删除了 RS 则无法回滚 > kubectl rollout pause deployment kubia # 暂停升级,在新 Pod 上进行金丝雀发布验证 > kubectl rollout resume deployment kubia # 恢复 9.4 结合探针控制升级速度

可配置 maxSurge 和 maxIUnavailable 来控制升级的最大超意外的 Pod 数量、最多容忍不可用 Pod 数量。

apiVersion: apps/v1 kind: Deployment metadata: name: kubia spec: replicas: 3 minReadySeconds: 10 # 新 Pod 就绪需等待 10s,才能继续滚动升级 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 只允许最多超出一个非预期 pod,即只能逐个更新 maxUnavailable: 0 # 不允许有不可用的 Pod,以确保新 Pod 能逐个替换旧的 Pod template: metadata: name: kubia labels: app: kubia spec: containers: - name: nodejs image: yinzige/kubia:v3 # 接收请求 5s 后返回 500 readinessProbe: periodSeconds: 1 # 定义 HTTP Get 就绪探针每隔 1s 执行一次 httpGet: port: 8080 path: / selector: matchLabels: app: kubia # apply 对象不存在则创建,否则修改对象属性 > kubectl apply -f kubia-deployment-v3-with-readinesscheck.yaml

使用上述 deployment 从正常版 v2 升级到 bug 版 v3,据配置 v3 的第一个 Pod 会创建并在第 5s 被 Service 标记为不可用,将其从 endpoint 中移除,请求不会分发到该 v3 Pod,10s 内未就绪,最终部署自动停止。

如下:kubia-54f54bf655 并未加入到 kubia Service 的 endpoints 中

> kubectl get endpoints NAME ENDPOINTS AGE kubia 172.17.0.12:8080,172.17.0.6:8080,172.17.0.8:8080 140m > kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE kubia-54f54bf655-tgvt9 0/1 Running 0 63s 172.17.0.7 m01 kubia-b669c877-8kx8c 1/1 Running 0 2m17s 172.17.0.6 m01 kubia-b669c877-957fl 1/1 Running 0 113s 172.17.0.12 m01 kubia-b669c877-n7hnb 1/1 Running 0 2m4s 172.17.0.8 m01 10. Stateful Set

场景:在有状态分部署存储应用中,Pod 的多副本有各自独立的 PVC 和 PV,pod 在新节点重建后需保证状态一致。

10.2 保证状态一致

一致的网络标识

sts 创建的 pod 名字后缀按顺序从 0 递增,通常通过 headless Service 暴露整个集群的 pod,每个 pod 有独立的 DNS 记录,pod 重建后保证名称和主机名一致。

一致的存储

sts 有 pod 模板和 PVC 模板,每个 pod 会绑定到唯一的 PVC 和 PV,重建后新 pod 会绑定到旧 PVC 和 PV 复用旧的存储。

10.3 使用 sts

三种必需资源:PV(若没有默认的 provisioner,则须手动创建)、控制 Service、sts 自身

创建 headless

apiVersion: v1 kind: Service metadata: name: kubia spec: clusterIP: None selector: app: kubia ports: - port: 80 name: http

创建 sts:PVC 模板会在 pod 运行前创建,并且绑定到默认 storage class 的 provisioner 创建的 PV 上

apiVersion: apps/v1 kind: StatefulSet metadata: name: kubia spec: serviceName: kubia # 绑定到 kubia 的 headless service replicas: 2 template: metadata: labels: app: kubia spec: containers: - name: kubia image: yinzige/kubia-pet ports: - containerPort: 8080 name: http volumeMounts: - mountPath: /var/data # PVC 绑定到 pod 目录 name: data volumeClaimTemplates: # 动态 PVC 模板,运行时提前创建 - metadata: name: data spec: resources: requests: storage: 1Mi accessModes: - ReadWriteOnce selector: matchLabels: app: kubia

注:sts 的创建或 scale 扩缩容,都是一次只操作一个 Pod 避免出现竞争、数据不一致的情况,pod 操作顺序与副本数顺序增减一致。

11. K8S 组件

k8s 中各组件通过 API 服务器的 event 事件流的通知机制进行解耦,各组件之间不会直接通信,相互透明。组件:

etcd:分布式一致性 KV 存储,只与 API Server 交互,存储集群各种资源元数据。

API Server:提供对集群资源的 CURD 接口,推送资源变更的事件流到监听端。

Scheduler:监听 Pod 创建事件,筛选出符合条件的节点并选出最优节点,更新 Pod 定义后发布给 API Server

各种资源的 Controller:监听资源更新的事件流,检查副本数,同样更新元数据发布给 API Server

kubectl:注册 Node 等待分配 Pod,告知 Docker 拉取镜像运行容器,随后向 API Server 会报 Pod 状态及指标

kube-proxy:代理 Service 暴露的 IP 和 Port

11.2 Pod 创建流程

各控制器间通过 API Server 进行解耦:

11.6 高可用

API Server 和 etcd 可有多节点。

为避免并发竞争,各控制器同一时间只能有一个实例运行,通过竞争写注解字段的方式进行选举,调度器同理。

12. API Server 安全


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3